package loan.calc.interceptor;

import loan.calc.exception.BizException;
import loan.calc.exception.ExceptionEnum;
import loan.calc.vo.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import javax.validation.ConstraintViolationException;
import javax.validation.UnexpectedTypeException;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

// 如果引入了swagger或knife4j的文档生成组件，这里需要仅扫描自己项目的包，否则文档无法正常生成
@RestControllerAdvice(basePackages = "loan.calc.controller")
@Slf4j
public class ResponseAdvice implements ResponseBodyAdvice<Object> {

    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        // 如果不需要进行封装的，可以添加一些校验手段，比如添加标记排除的注解
        return true;
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        // 提供一定的灵活度，如果body已经被包装了，就不进行包装
        if (body instanceof R) {
            return body;
        }
        return R.success().putValue("data", body);
    }

    @ExceptionHandler({BizException.class})
    public R handleBizException(BizException e) {
        log.error("[业务异常]", e);
        return new R(e.getCode().toString(), e.getMessage());
    }

    @ExceptionHandler({MethodArgumentNotValidException.class})
    public R handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
        log.error("[全局异常处理] [参数校验不通过]{}", ex.getMessage(), ex);
        BindingResult bindingResult = ex.getBindingResult();
        List<FieldError> fieldErrors = bindingResult.getFieldErrors();
        Map<String, Set<String>> errors = fieldErrors.stream()
                .collect(
                        Collectors.groupingBy(
                                FieldError::getField,
                                Collectors.mapping(FieldError::getDefaultMessage, Collectors.toSet())
                        )
                );
        return new R(ExceptionEnum.ILLEGAL_ARGS_EX.getCode().toString(), ExceptionEnum.ILLEGAL_ARGS_EX.getDefaultMessage())
                .putValue("errors", errors);

    }

    /**
     * {@code @PathVariable} 和 {@code @RequestParam} 参数校验不通过时抛出的异常处理
     */
    @ExceptionHandler({ConstraintViolationException.class})
    public R handleConstraintViolationException(ConstraintViolationException ex) {
        log.error("[全局异常处理] [参数校验不通过]{}", ex.getMessage(), ex);
        if (StringUtils.hasText(ex.getMessage())) {
            new R(ExceptionEnum.ILLEGAL_ARGS_EX.getCode().toString(), ex.getMessage());
        }
        return new R(ExceptionEnum.ILLEGAL_ARGS_EX.getCode().toString(), ExceptionEnum.ILLEGAL_ARGS_EX.getDefaultMessage());
    }

    /**
     * 方法参数校验异常[类型不配备]
     */
    @ExceptionHandler(UnexpectedTypeException.class)
    public R handleUnexpectedTypeException(UnexpectedTypeException e) {
        log.error("[全局异常处理] [参数校验类型不匹配]{}", e.getMessage(), e);
        return new R(ExceptionEnum.ILLEGAL_ARGS_EX.getCode().toString(), "校验参数类型异常，请检查注释使用是否正确，" + e.getMessage());
    }


    /**
     * 前端传递参数验证异常
     * @param ex
     * @return
     */
    @ExceptionHandler(BindException.class)
    public R handleBindException(BindException ex) {
        log.warn("[全局异常处理] [提交参数校不符合规则]{}", ex.getMessage(), ex);
        BindingResult bindingResult = ex.getBindingResult();
        List<FieldError> fieldErrors = bindingResult.getFieldErrors();
        Map<String, Set<String>> errors = fieldErrors.stream()
                .collect(
                        Collectors.groupingBy(
                                FieldError::getField,
                                Collectors.mapping(FieldError::getDefaultMessage, Collectors.toSet())
                        )
                );
        return new R(ExceptionEnum.ILLEGAL_ARGS_EX.getCode().toString(), ExceptionEnum.ILLEGAL_ARGS_EX.getDefaultMessage())
                .putValue("errors", errors);
    }

    /**
     * 顶级异常捕获并统一处理，当其他异常无法处理时候选择使用
     */
    @ExceptionHandler({Exception.class})
    public R handle(Exception ex) {
        log.error("[全局异常处理] [系统异常]", ex);
        return new R(ExceptionEnum.SYS_ERR.getCode().toString(), ExceptionEnum.SYS_ERR.getDefaultMessage());
    }

}
